//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_MAT_CMAT_H_
#define _BUOLA_MAT_CMAT_H_

#include <buola/mat/cexpr.h>
#include <buola/mat/detail/cstorage.h>
#include <buola/mat/detail/assign.h>

namespace buola { namespace mat {

///\addtogroup matrix
///@{
    
DECLARE_NAME(Size)
DECLARE_NAME(Identity)

///\cond
namespace detail {
    
    template<typename tScalar,size_t vRows,size_t vCols>
    struct MMatrixTraits<CMat<tScalar,vRows,vCols>>
    {
        typedef tScalar TScalar;                        ///<the scalar underlying type
        static const size_t cRows=vRows;                ///<number of rows if known at compile time
        static const size_t cCols=vCols;                ///<number of columns if known at compile time

        static const bool cRowLinearAccess=true;        ///<whether rows can be accessed linearly
        static const bool cColLinearAccess=(vCols==1);  ///<whether columns can be accessed linearly
        static const bool cRowPacketAccess=true;        ///<whether rows can be accessed as packets
        static const bool cColPacketAccess=(vCols==1);  ///<whether columns can be accessed as packets
        static const bool cMightAlias=false;            ///<whether it could alias another matrix in a bad way
        static const int cElementAccess=ACCESS_DIRECT;  ///<elements can be accessed one by one
        static const bool cEigenMappable=true;          ///<can be directly mapped to an eigen type
        static const bool cKeepConstness=true;          ///<whether constness matters and should be preserved on nesting
        static const bool cExprAssignable=false;
        static const bool cIsMatrix=true;
    };

/*namespace detail*/ }
///\endcond

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// matrix
///
/// \tparam tScalar the type of each component
/// \tparam vRows the number of rows in the matrix, if known at compile time. If it is not known at compile time,
///               the special value \c cDynamic can be specified, which means that the size of the matrix can be
///               changed at runtime. This is the default.
/// \tparam vCols the number of columns in the matrix, if known at compile time. If it is not known at compile time,
///               the special value \c cDynamic can be specified, which means that the size of the matrix can be
///               changed at runtime. This is the default.
///
/// It is recommended to use cDynamic only for small matrices (a maximum of size 4 in each direction), otherwise it
/// can lead to template bloating.
///
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template<typename tScalar,size_t vRows,size_t vCols>
class CMat : private detail::CStorage<tScalar,vRows,vCols>,
             public CExpr<CMat<tScalar,vRows,vCols>>
{
    typedef detail::CStorage<tScalar,vRows,vCols> TStorage;
    typedef CMat<tScalar,vRows,vCols> TSelf;
    
public:
///\name initialization    
//@{
    ///default constructor
    ///
    ///doesn't initialize any component
    ///
    ///in the case of dynamic-size matrices or vectors, sets the size to 0
    CMat()=default;

    ///size initialization
    ///
    ///Only for dynamic-size vectors
    template<bool b=((vRows==cDynamic&&vCols!=cDynamic)||(vCols==cDynamic&&vRows!=cDynamic)),
                                                    typename std::enable_if<b,int>::type=0>
    explicit CMat(size_t pRowsOrCols)
        :   TStorage(pRowsOrCols)
    {
    }
    
    ///size initialization
    ///
    ///Only for dynamic-size matrices
    template<bool b=(vRows==cDynamic&&vCols==cDynamic),typename std::enable_if<b,int>::type=0>
    explicit CMat(size_t pRows,size_t pCols)
        :   TStorage(pRows,pCols)
    {}

    ///explicit size initialization
    ///
    ///Works with any matrix type, even fixed-size ones.
    ///
    ///Both the number of rows and columns must be specified, even if one of them is fixed.
    explicit CMat(NSize,size_t pRows,size_t pCols)
        :   TStorage(pRows,pCols)
    {}

    ///initializer-list initialization of vector
    CMat(std::initializer_list<tScalar> pList)
        :   TStorage(pList.size())
    {
        static_assert(vRows==1||vCols==1||(vCols!=cDynamic&&vRows!=cDynamic),
                      "only vectors and fixed-size matrices can be initialized with initializer-list");
        assert(size()==pList.size()||(vRows==1&&(vCols==cDynamic||vCols==pList.size()))||(vCols==1&&(vRows==cDynamic||vRows==pList.size())));
        for(int i=0;i<size();i++)
            TStorage::At(i)=pList.begin()[i];
    }

    ///initializer-list initialization of matrix
    CMat(std::initializer_list<std::initializer_list<tScalar>> pArray)
        :   TStorage(pArray.size(),pArray.begin()[0].size())
    {
        assert(vRows==cDynamic||vRows==pArray.size());

        int r=0;
        for(auto &i : pArray)
        {
            int c=0;
            assert(i.size()==Cols());
            for(auto j : i)
                TStorage::At(r,c++)=j;
            r++;
        }
    }

    ///initialization from pointer to data
    ///
    ///Can only be used with fixed-sized matrices
    ///
    ///The data is copied
    explicit CMat(const tScalar *pList)
    {
        static_assert(vCols!=cDynamic&&vRows!=cDynamic,
                      "only fixed-size matrices and vectors can be initialized from pointer");
        for(int i=0;i<size();i++)
            TStorage::At(i)=pList[i];
    }

    ///initialization to zero
    ///
    ///Can only be used with fixed-sized matrices
    CMat(NZero)
    {
        static_assert(vCols!=cDynamic&&vRows!=cDynamic,
                      "only fixed-size matrices and vectors can be initialized with nZero");
        for(int i=0;i<size();i++)
            TStorage::At(i)=0;
    }

    ///initialization of vector of length 2
    template<bool b=(vRows*vCols==2),typename std::enable_if<b,int>::type=0>
    explicit CMat(tScalar px,tScalar py)
    {
        this->x()=px;
        this->y()=py;
    }
    
    ///initialization of vector of length 3
    template<bool b=(vRows*vCols==3),typename std::enable_if<b,int>::type=0>
    explicit CMat(tScalar px,tScalar py,tScalar pz)
    {
        this->x()=px;
        this->y()=py;
        this->z()=pz;
    }
    
    ///initialization of vector of length 4
    template<bool b=(vRows*vCols==4),typename std::enable_if<b,int>::type=0>
    explicit CMat(tScalar px,tScalar py,tScalar pz,tScalar pw)
    {
        this->x()=px;
        this->y()=py;
        this->z()=pz;
        this->w()=pw;
    }

    ///copy constructor (defaulted)
    CMat(const CMat &pRH)=default;
    ///move constructor (defaulted)
    CMat(CMat &&pRH)=default;

    ///initialization from expression
    ///
    ///The expression is evaluated directly into the matrix
    template<typename tRH,OEnableIf<detail::QSizesCompatible<TSelf,tRH>> =0>
    CMat(const CExpr<tRH> &pRH)
        :   TStorage(pRH.Derived().Rows(),pRH.Derived().Cols())
    {
        detail::assign<tScalar>(*this,pRH.Derived());
    }

    ///initialization from matrix of different type
    template<typename tRHScalar,size_t vRHRows,size_t vRHCols,OEnableIfNot<std::is_same<tScalar,tRHScalar>> =0>
    explicit CMat(const CMat<tRHScalar,vRHRows,vRHCols> &pRH)
        :   TStorage(pRH.Rows(),pRH.Cols())
    {
        for(int r=0;r<Rows();r++)
            for(int c=0;c<Cols();c++)
                At<tScalar>(r,c)=pRH.At<tRHScalar>(r,c);
    }
//@}

///\name reshaping
//@{
    ///remove a row from the matrix
    ///
    ///Only possible if the number of rows is not fixed
    void RemoveRow(size_t pRow)
    {
        static_assert(vRows==cDynamic,"can't remove row from fixed-rows matrix");
        for(int r=pRow+1;r<Rows();r++)
            for(int c=0;c<Cols();c++)
                TStorage::At(r-1,c)=TStorage::At(r,c);
        TStorage::Shrink(Rows()-1,Cols());
    }
    
    ///remove a column from the matrix
    ///
    ///Only possible if the number of columns is not fixed
    void RemoveCol(size_t pCol)
    {
        static_assert(vCols==cDynamic,"can't remove column from fixed-columns matrix");
        size_t lNewCols=Cols()-1;
        for(int r=0;r<Rows();r++)
        {
            for(int c=0;c<pCol;c++)
                TStorage::At(lNewCols*r+c)=TStorage::At(r,c);
            for(int c=pCol+1;c<Cols();c++)
                TStorage::At(lNewCols*r+c-1)=TStorage::At(r,c);
        }
        TStorage::Shrink(Rows(),Cols()-1);
    }
//@}    
    
    template<typename tType>
    tScalar &At(size_t r,size_t c)
    {
        static_assert(std::is_same<ODecay<tType>,tScalar>::value,"wrong type in scalar access");
        return TStorage::At(r,c);
    }
    
    template<typename tType>
    tScalar At(size_t r,size_t c) const
    {
        static_assert(std::is_same<tType,tScalar>::value,"wrong type in scalar access");
        return TStorage::At(r,c);
    }

    template<typename tType>
    tScalar &At(size_t i)
    {
        static_assert(std::is_same<ODecay<tType>,tScalar>::value,"wrong type in scalar access");
        return TStorage::At(i);
    }

    template<typename tType>
    tScalar At(size_t i) const
    {
        static_assert(std::is_same<tType,tScalar>::value,"wrong type in scalar access");
        return TStorage::At(i);
    }

    using TStorage::Rows;
    using TStorage::Cols;
    using TStorage::size;
    using TStorage::Resize;

    using TStorage::Data;
    using TStorage::Stride;

    ///copy assignment operator
    CMat &operator=(const CMat &pRH)=default;
    ///move assignment operator
    CMat &operator=(CMat &&pRH)=default;

    ///assignment of expression
    template<typename tDerived>
    OEnableIf<detail::QMightAlias<tDerived>,CMat&> operator=(const CExpr<tDerived> &pRH)
    {
        *this=TSelf(pRH.Derived());
        return *this;
    }

    ///assignment of expression
    template<typename tDerived>
    OEnableIfNot<detail::QMightAlias<tDerived>,CMat&> operator=(const CExpr<tDerived> &pRH)
    {
        TStorage::Resize(pRH.Derived().Rows(),pRH.Derived().Cols());
        detail::assign<tScalar>(*this,pRH.Derived());
        return *this;
    }

    ///assignment to identity
    template<bool b=(vRows!=cDynamic&&vRows==vCols),typename std::enable_if<b,int>::type=0>
    CMat &operator=(NIdentity)
    {
        for(int i=0;i<vRows;i++)
            for(int j=0;j<vCols;j++)
                At<tScalar>(i,j)=(i==j)?1.0:0.0;
        return *this;
    }
};


template<typename tScalar,size_t vRows,size_t vCols>
inline io::CTextReader &operator>>(io::CTextReader &pReader,CMat<tScalar,vRows,vCols> &pMat)
{
    static_assert(vRows!=cDynamic&&vCols!=cDynamic,"can only read into fixed size matrix");
    
    for(int i=0;i<pMat.size();i++)
    {
        pReader >> pMat.template At<tScalar>(i);
    }
    
    return pReader;
}

///column vector
///
///\tparam tScalar the type of each component
///\tparam vSize the length of the vector (defaults to dynamic size)
template<typename tScalar,size_t vSize=cDynamic>
using CVec=CMat<tScalar,vSize,1>;

///row vector
///
///\tparam tScalar the type of each component
///\tparam vSize the length of the vector (defaults to dynamic size)
template<typename tScalar,size_t vSize=cDynamic>
using CRow=CMat<tScalar,1,vSize>;

///scalar (1x1) matrix
///
///\tparam tScalar the type of the single component
template<typename tScalar>
using CScalar=CMat<tScalar,1,1>;

///column vector of type \c double
///
///\tparam vSize the length of the vector
template<size_t vSize>
using CVecNd=CMat<double,vSize,1>;

///row vector of type \c double
///
///\tparam vSize the length of the vector
template<size_t vSize>
using CRowNd=CMat<double,1,vSize>;

///column vector of type \c float
///
///\tparam vSize the length of the vector
template<size_t vSize>
using CVecNf=CMat<float,vSize,1>;

///row vector of type \c float
///
///\tparam vSize the length of the vector
template<size_t vSize>
using CRowNf=CMat<float,1,vSize>;

///column vector of type \c int
///
///\tparam vSize the length of the vector
template<size_t vSize>
using CVecNi=CMat<int,vSize,1>;

///row vector of type \c int
///
///\tparam vSize the length of the vector
template<size_t vSize>
using CRowNi=CMat<int,1,vSize>;

///dynamic-size matrix of type \c double
typedef CMat<double> CMat_d;
///column vector of dynamic length and type \c double
typedef CVec<double> CVec_d;
///row vector of dynamic length and type \c double
typedef CRow<double> CRow_d;
///scalar (1x1 matrix) of type \c double
typedef CScalar<double> CScalar_d;

///dynamic-size matrix of type \c float
typedef CMat<float> CMat_f;
///column vector of dynamic length and type \c float
typedef CVec<float> CVec_f;
///row vector of dynamic length and type \c float
typedef CRow<float> CRow_f;
///scalar (1x1 matrix) of type \c float
typedef CScalar<float> CScalar_f;

///dynamic-size matrix of type \c int
typedef CMat<int> CMat_i;
///column vector of dynamic length and type \c int
typedef CVec<int> CVec_i;
///row vector of dynamic length and type \c int
typedef CRow<int> CRow_i;
///scalar (1x1 matrix) of type \c int
typedef CScalar<int> CScalar_i;

///dynamic-size matrix of type \c bool
typedef CMat<bool> CMat_b;
///column vector of dynamic length and type \c bool
typedef CVec<bool> CVec_b;
///row vector of dynamic length and type \c bool
typedef CRow<bool> CRow_b;

///2x3 matrix of type \c double
typedef CMat<double,2,3> CMat23d;
///3x4 matrix of type \c double
typedef CMat<double,3,4> CMat34d;
///2x2 matrix of type \c double
typedef CMat<double,2,2> CMat2d;
///3x3 matrix of type \c double
typedef CMat<double,3,3> CMat3d;
///4x4 matrix of type \c double
typedef CMat<double,4,4> CMat4d;
///column vector of length 2 and type \c double
typedef CVec<double,2> CVec2d;
///column vector of length 3 and type \c double
typedef CVec<double,3> CVec3d;
///column vector of length 4 and type \c double
typedef CVec<double,4> CVec4d;
///row vector of length 2 and type \c double
typedef CRow<double,2> CRow2d;
///row vector of length 3 and type \c double
typedef CRow<double,3> CRow3d;
///row vector of length 4 and type \c double
typedef CRow<double,4> CRow4d;

///2x3 matrix of type \c float
typedef CMat<float,2,3> CMat23f;
///3x4 matrix of type \c float
typedef CMat<float,3,4> CMat34f;
///2x2 matrix of type \c float
typedef CMat<float,2,2> CMat2f;
///3x3 matrix of type \c float
typedef CMat<float,3,3> CMat3f;
///4x4 matrix of type \c float
typedef CMat<float,4,4> CMat4f;
///column vector of length 2 and type \c float
typedef CVec<float,2> CVec2f;
///column vector of length 3 and type \c float
typedef CVec<float,3> CVec3f;
///column vector of length 4 and type \c float
typedef CVec<float,4> CVec4f;
///row vector of length 2 and type \c float
typedef CRow<float,2> CRow2f;
///row vector of length 3 and type \c float
typedef CRow<float,3> CRow3f;
///row vector of length 4 and type \c float
typedef CRow<float,4> CRow4f;

template<typename tScalar,typename tRange>
mat::CVec<tScalar> make_vec(const tRange &pRange)
{
    mat::CVec<tScalar> lResult(pRange.size());
    
    for(int i=0;i<lResult.Rows();i++)
        lResult[i]=pRange[i];
    
    return lResult;
}

///@}

/*namespace mat*/ } /*namespace buola*/ }

#endif
